package com.sz.netty.server.handler;



import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessagePropertiesBuilder;
import com.sz.netty.entity.DtuFtpMessageLog;
import com.sz.netty.entity.DtuFtpMessageDetailSim;
import com.sz.netty.entity.DtuFtpMessageDetail;
import com.sz.netty.entity.DtuFtpInfo;
import com.sz.netty.service.DtuFtpMessageLogService;
import com.sz.netty.service.DtuFtpMessageDetailSimService;
import com.sz.netty.service.DtuFtpMessageDetailService;
import com.sz.netty.service.DtuFtpInfoService;
import com.sz.netty.service.DeviceCommandService;
import com.sz.netty.transport.TransportAnalyzer;
import com.sz.netty.util.ProtocolDataParser;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Handler for processing binary data with the specified protocol:
 * 
 * Header: 3 bytes - Fixed value: 0x7b 0x7b 0x7b (Start of message indicator)
 * Type: 1 byte - Fixed value: 0x88 (Unified message type identifier)
 * Device ID: 6 bytes - 6-digit unique device identifier
 * Packet Length: 2 bytes - Frame byte count (including header and trailer, excluding end marker)
 * Content: <1024 bytes - Variable based on instruction type
 * Checksum: 1 byte - XOR result of all bytes from header to checksum
 * Trailer: 3 bytes - Fixed value: 0x7d 0x7d 0x7d (End of message indicator)
 * End Marker: 2 bytes - Fixed value: 0x0d 0x0a (Carriage return line feed)
 */
@Slf4j
@Component
public class BinaryDataHandler extends SimpleChannelInboundHandler<byte[]> {
    
    // Autowired services for database operations
    private static DtuFtpMessageLogService dtuFtpMessageLogService;
    private static DtuFtpMessageDetailSimService dtuFtpMessageDetailSimService;
    private static DtuFtpMessageDetailService dtuFtpMessageDetailService;
    private static DtuFtpInfoService dtuFtpInfoService;
    private static DeviceCommandService deviceCommandService;
    private static RabbitTemplate rabbitTemplate;
    
    @Autowired
    public void setDtuFtpMessageLogService(DtuFtpMessageLogService dtuFtpMessageLogService) {
        BinaryDataHandler.dtuFtpMessageLogService = dtuFtpMessageLogService;
    }
    
    @Autowired
    public void setDtuFtpMessageDetailSimService(DtuFtpMessageDetailSimService dtuFtpMessageDetailSimService) {
        BinaryDataHandler.dtuFtpMessageDetailSimService = dtuFtpMessageDetailSimService;
    }
    
    @Autowired
    public void setDtuFtpMessageDetailService(DtuFtpMessageDetailService dtuFtpMessageDetailService) {
        BinaryDataHandler.dtuFtpMessageDetailService = dtuFtpMessageDetailService;
    }
    
    @Autowired
    public void setDtuFtpInfoService(DtuFtpInfoService dtuFtpInfoService) {
        BinaryDataHandler.dtuFtpInfoService = dtuFtpInfoService;
    }
    
    @Autowired
    public void setDeviceCommandService(DeviceCommandService deviceCommandService) {
        BinaryDataHandler.deviceCommandService = deviceCommandService;
    }
    
    @Autowired
    public void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
        BinaryDataHandler.rabbitTemplate = rabbitTemplate;
    }
    
    // Protocol constants
    private static final byte[] HEADER = {0x7b, 0x7b, 0x7b};
    private static final byte TYPE_CODE = (byte) 0x88;
    private static final byte[] TRAILER = {0x7d, 0x7d, 0x7d};
    private static final byte[] END_MARKER = {0x0d, 0x0a};
    private static final int DEVICE_ID_LENGTH = 6;
    private static final int PACKET_LENGTH_FIELD_LENGTH = 2;
    private static final int CHECKSUM_LENGTH = 1;
    private static final int MAX_CONTENT_LENGTH = 1024;
    
    // Store channel contexts by channel ID for sending messages back to devices
    private static final Map<String, ChannelHandlerContext> channelContextMap = new ConcurrentHashMap<>();
    
    // Store device ID to channel ID mapping for easier lookup
    private static final Map<String, String> deviceToChannelMap = new ConcurrentHashMap<>();
    
    // Store pending commands that are waiting for acknowledgment
    private static final Map<String, Long> pendingCommands = new ConcurrentHashMap<>();
    
    // Timeout for command acknowledgment (in milliseconds)
    private static final long COMMAND_ACK_TIMEOUT = 5000; // 5 seconds
    
    // Log file directory
    private static final String LOG_DIRECTORY = "logs/netty-log";
    
    // Buffer to accumulate incoming data
    private byte[] buffer = new byte[0];
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
        // Log received raw data
        log.debug("Received raw data: {}", bytesToHex(msg));
        log.info("Received {} bytes of raw data on port 8089", msg.length);
        
        // Explicitly log that all data received on port 8089 is being captured
        log.info("Port 8089: Capturing all incoming data - {} bytes received", msg.length);
        
        // Save all received data immediately to ensure no data is lost
        saveDataToFile("receive", msg, "Raw data received");
        
        // Save all received data to netty-log/all directory with timestamp
        saveAllDataToFile(msg);
        
        // Append received data to buffer
        buffer = concatenate(buffer, msg);
        
        // Process complete messages in buffer
        processBuffer(ctx);
    }
    
    /**
     * Process buffered data to extract complete messages
     */
    private void processBuffer(ChannelHandlerContext ctx) {
        while (buffer.length >= HEADER.length + 1 + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + CHECKSUM_LENGTH + TRAILER.length) {
            // Find header
            int headerIndex = findHeader();
            if (headerIndex == -1) {
                // No header found, discard buffer
                if (buffer.length > 0) {
                    log.warn("Discarding buffer without valid header: {}", bytesToHex(buffer));
                    // Save discarded data to log file
                    saveDataToFile("discarded", buffer, "Discarded buffer without valid header");
                }
                buffer = new byte[0];
                return;
            }
            
            // Move buffer to start from header
            if (headerIndex > 0) {
                byte[] skippedData = Arrays.copyOfRange(buffer, 0, headerIndex);
                log.warn("Skipping {} bytes before header: {}", headerIndex, bytesToHex(skippedData));
                // Save skipped data to log file
                saveDataToFile("skipped", skippedData, "Skipped data before header");
                buffer = Arrays.copyOfRange(buffer, headerIndex, buffer.length);
            }
            
            // Check if we have enough data for packet length field
            if (buffer.length < HEADER.length + 1 + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH) {
                // Not enough data, wait for more
                return;
            }
            
            // Extract packet length (big endian)
            int packetLength = ((buffer[HEADER.length + 1 + DEVICE_ID_LENGTH] & 0xFF) << 8) |
                               (buffer[HEADER.length + 1 + DEVICE_ID_LENGTH + 1] & 0xFF);
            
            // Validate packet length
            if (packetLength <= 0 || packetLength > HEADER.length + TRAILER.length + DEVICE_ID_LENGTH + 
                PACKET_LENGTH_FIELD_LENGTH + CHECKSUM_LENGTH + MAX_CONTENT_LENGTH) {
                // Invalid packet length, skip this header
                log.warn("Invalid packet length: {}", packetLength);
                buffer = Arrays.copyOfRange(buffer, 1, buffer.length);
                continue;
            }
            
            // Check if we have the complete packet
            // Calculate minimum required length (without end marker)
            int minRequiredLength = packetLength;
            // Calculate maximum possible length (with end marker)
            int maxPossibleLength = packetLength + END_MARKER.length;
            
            // If we don't have enough data for even the minimum packet, wait for more
            if (buffer.length < minRequiredLength) {
                return;
            }
            
            // Determine the actual packet length by checking what's in the buffer
            int actualPacketLength = minRequiredLength;
            
            // Check if there's more data that might include an end marker
            if (buffer.length >= maxPossibleLength) {
                // Check if it ends with the standard end marker (0x0D 0x0A)
                if (buffer[maxPossibleLength - 2] == 0x0D && buffer[maxPossibleLength - 1] == 0x0A) {
                    actualPacketLength = maxPossibleLength;
                }
                // But ignore packets that end with 0x7B 0x7B (don't treat them as end markers)
            }
            
            // Extract complete packet
            byte[] packet = Arrays.copyOfRange(buffer, 0, actualPacketLength);
            buffer = Arrays.copyOfRange(buffer, actualPacketLength, buffer.length);
            
            // Process the packet
            processPacket(ctx, packet);
        }
    }
    
    /**
     * Find header in buffer
     */
    private int findHeader() {
        for (int i = 0; i <= buffer.length - HEADER.length; i++) {
            boolean match = true;
            for (int j = 0; j < HEADER.length; j++) {
                if (buffer[i + j] != HEADER[j]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * Process a complete packet
     */
    private void processPacket(ChannelHandlerContext ctx, byte[] packet) {
        try {
            log.info("Processing packet of {} bytes", packet.length);
            
            // Validate packet structure
            if (!validatePacket(packet)) {
                log.warn("Invalid packet received: {}", bytesToHex(packet));
                // Save invalid packet to log file
                saveDataToFile("invalid", packet, "Invalid packet");
                return;
            }
            
            // Extract fields
            byte[] deviceId = Arrays.copyOfRange(packet, HEADER.length + 1, HEADER.length + 1 + DEVICE_ID_LENGTH);
            int packetLength = ((packet[HEADER.length + 1 + DEVICE_ID_LENGTH] & 0xFF) << 8) |
                              (packet[HEADER.length + 1 + DEVICE_ID_LENGTH + 1] & 0xFF);
            byte[] content = extractContent(packet);
            
            // Log received data
            log.info("Received data from device {}: content length = {}", 
                     bytesToHex(deviceId), content.length);
            
            // Update device ID in connection info
            updateDeviceIdInConnectionInfo(ctx, deviceId);
            
            // Determine end marker type for logging
            byte[] endMarker = detectEndMarker(packet);
            
            // Log detailed packet information
            log.debug("Packet details:");
            log.debug("  Header: {}", bytesToHex(Arrays.copyOfRange(packet, 0, HEADER.length)));
            log.debug("  Type: {}", String.format("0x%02X", packet[HEADER.length]));
            log.debug("  Device ID: {}", bytesToHex(deviceId));
            log.debug("  Packet Length: {}", packetLength);
            log.debug("  Content: {}", bytesToHex(content));
            log.debug("  Checksum: {}", String.format("0x%02X", packet[packet.length - endMarker.length - TRAILER.length - CHECKSUM_LENGTH]));
            log.debug("  Trailer: {}", bytesToHex(Arrays.copyOfRange(packet, packet.length - endMarker.length - TRAILER.length, packet.length - endMarker.length)));
            log.debug("  End Marker: {}", bytesToHex(Arrays.copyOfRange(packet, packet.length - endMarker.length, packet.length)));
            
            // Save valid packet to log file
            saveDataToFile("device-" + bytesToHex(deviceId).replace(" ", ""), packet, "Valid packet from device " + bytesToHex(deviceId));
            
            // Save valid protocol-compliant data by device ID and date
            saveValidDataByDevice(deviceId, packet);
            
            // Handle different command types
            if (content.length > 0) {
                int commandType = content[0] & 0xFF;
                switch (commandType) {
                    case 0x01: // Heartbeat/Handshake command
                        handleHeartbeatCommand(ctx, deviceId, content);
                        break;
                    case 0x02: // GPS data upload command
                        // Save parsed data to database
                        // saveDataToDatabase(packet);
                        // Send response for data upload
                        handleDataUploadResponse(ctx, deviceId, content);
                        break;
                    case 0x04: // Display command acknowledgment
                        handleDisplayCommandAcknowledgment(deviceId, content);
                        break;
                    default:
                        log.warn("Unknown command type: 0x{}", String.format("%02X", commandType));
                        // Save unknown command data to database
                        // saveDataToDatabase(packet);
                        break;
                }
            } else {
                log.warn("Empty content in packet from device {}", bytesToHex(deviceId));
            }
            
            // Here you would process the content based on your business logic
            // For now, we'll just log it
            log.debug("Content as string: {}", new String(content));
            
        } catch (Exception e) {
            log.error("Error processing packet: {}", bytesToHex(packet), e);
            // Save error packet to log file
            saveDataToFile("error", packet, "Error processing packet: " + e.getMessage());
        }
    }
    
    /**
     * Handle heartbeat command (0x01) from device
     * @param ctx Channel handler context
     * @param deviceId Device ID
     * @param content Content bytes (including command byte)
     */
    private void handleHeartbeatCommand(ChannelHandlerContext ctx, byte[] deviceId, byte[] content) {
        try {
            log.info("Handling heartbeat command from device {}", bytesToHex(deviceId));
            
            // Parse SIM card information from heartbeat packet
            if (content.length >= 4) { // Minimum length: command(1) + signal(1) + length(1) + sim(1)
                // Signal strength (byte 2)
                int signalLevel = content[1] & 0xFF;
                
                // SIM card number length (byte 3)
                int simNumberLength = content[2] & 0xFF;
                
                // SIM card number content (starting from byte 4)
                if (content.length >= 3 + simNumberLength) {
                    byte[] simNumberBytes = Arrays.copyOfRange(content, 3, 3 + simNumberLength);
                    String simNumber = new String(simNumberBytes, java.nio.charset.StandardCharsets.US_ASCII).trim();
                    
                    log.info("Heartbeat from device {}: signal={}, SIM={}", 
                             bytesToHex(deviceId), signalLevel, simNumber);
                }
            }
            
            // Send heartbeat response back to device
            sendHeartbeatResponse(ctx, deviceId);
            
        } catch (Exception e) {
            log.error("Error handling heartbeat command from device {}: {}", 
                      bytesToHex(deviceId), e.getMessage(), e);
        }
    }
    
    /**
     * Handle data upload response (0x02) to device
     * @param ctx Channel handler context
     * @param deviceId Device ID
     * @param content Content bytes (including command byte)
     */
    private void handleDataUploadResponse(ChannelHandlerContext ctx, byte[] deviceId, byte[] content) {
        try {
            log.info("Handling data upload response for device {}", bytesToHex(deviceId));
            
            // Extract packet sequence number (byte 2) and record count (byte 3)
            if (content.length >= 3) {
                byte packetSeq = content[1];  // Data packet sequence number
                byte recordCount = content[2]; // Data record count
                
                // Create response content according to protocol:
                // Command byte (0x02) + Packet sequence number + Record count
                byte[] responseContent = {0x02, packetSeq, recordCount};
                
                // Send response back to device
                sendResponse(ctx, deviceId, responseContent, "Data upload response");
            } else {
                log.warn("Insufficient content length for data upload response from device {}", bytesToHex(deviceId));
            }
        } catch (Exception e) {
            log.error("Error handling data upload response for device {}: {}", 
                      bytesToHex(deviceId), e.getMessage(), e);
        }
    }
    
    /**
     * Handle display command acknowledgment (0x04) from device
     * @param deviceId Device ID
     * @param content Content bytes (including command byte and result)
     */
    private void handleDisplayCommandAcknowledgment(byte[] deviceId, byte[] content) {
        try {
            log.info("Handling display command acknowledgment for device {}", bytesToHex(deviceId));
            
            // Save the acknowledgment response to the specified directory
            saveDisplayAckResponseToFile(deviceId, content);
            
            // Check if we have enough content (command byte + result byte)
            if (content.length >= 2) {
                byte result = content[1]; // Result byte: 0x01 - success, 0x00 - failure
                
                // Extract the display content that was acknowledged (3 bytes after command byte)
                if (content.length >= 5) { // command(1) + result(1) + display_content(3)
                    byte[] displayContent = new byte[3];
                    System.arraycopy(content, 2, displayContent, 0, 3);
                    
                    String deviceIdStr = new String(deviceId, java.nio.charset.StandardCharsets.US_ASCII).trim();
                    processCommandAcknowledgment(deviceIdStr, displayContent);
                    
                    if (result == 0x01) {
                        log.info("Display command acknowledgment from device {}: SUCCESS for content {}", 
                                bytesToHex(deviceId), bytesToHex(displayContent));
                    } else {
                        log.warn("Display command acknowledgment from device {}: FAILURE for content {}", 
                                bytesToHex(deviceId), bytesToHex(displayContent));
                    }
                } else {
                    log.warn("Insufficient content length for display command acknowledgment from device {}", bytesToHex(deviceId));
                }
            } else {
                log.warn("Insufficient content length for display command acknowledgment from device {}", bytesToHex(deviceId));
            }
        } catch (Exception e) {
            log.error("Error handling display command acknowledgment for device {}: {}", 
                      bytesToHex(deviceId), e.getMessage(), e);
        }
    }
    
    /**
     * Send heartbeat response (0x01) to device
     * @param ctx Channel handler context
     * @param deviceId Device ID
     */
    private void sendHeartbeatResponse(ChannelHandlerContext ctx, byte[] deviceId) {
        try {
            // Create heartbeat response packet according to protocol
            // Response format: command byte (0x01) only
            byte[] responseContent = {0x01};
            
            // Send response
            sendResponse(ctx, deviceId, responseContent, "Heartbeat response");
        } catch (Exception e) {
            log.error("Error sending heartbeat response to device {}: {}", 
                      bytesToHex(deviceId), e.getMessage(), e);
        }
    }
    
    /**
     * Send response to device
     * @param ctx Channel handler context
     * @param deviceId Device ID
     * @param responseContent Response content bytes
     * @param description Description for logging
     */
    private void sendResponse(ChannelHandlerContext ctx, byte[] deviceId, byte[] responseContent, String description) {
        try {
            // Create complete packet
            byte[] responsePacket = createResponsePacket(deviceId, responseContent);
            
            // Send response
            ctx.writeAndFlush(responsePacket);
            log.info("Sent {} to device {}: {}", 
                     description, bytesToHex(deviceId), bytesToHex(responsePacket));
            
            // Save sent data to log file in the specified directory
            saveResponseDataToFile(bytesToHex(deviceId).replace(" ", ""), responsePacket, description);
        } catch (Exception e) {
            log.error("Error sending {} to device {}: {}", 
                      description, bytesToHex(deviceId), e.getMessage(), e);
        }
    }
    
    /**
     * Save response data to file in netty-log/send/res directory, organized by device and date
     * @param deviceId Device ID
     * @param data Data that was sent
     * @param description Description of the data
     */
    private static void saveResponseDataToFile(String deviceId, byte[] data, String description) {
        try {
            // Create directory structure: netty-log/send/res/{device_id}/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            Path logDir = Paths.get("netty-log", "send", "res", deviceId, dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the directory structure
            String filename = String.format("netty-log/send/res/%s/%s/sent_data_%s.txt", deviceId, dateStr, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Description: %s%n", description));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHexStatic(data)));
                writer.write(String.format("String Data: %s%n", new String(data, java.nio.charset.StandardCharsets.US_ASCII).replaceAll("[^\\p{Print}]", ".")));
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved response data to file: {}", filename);
        } catch (IOException e) {
            log.error("Failed to save response data to file", e);
        }
    }
    
    /**
     * Create a complete response packet according to protocol
     * @param deviceId Device ID
     * @param content Content bytes
     * @return Complete packet bytes
     */
    private byte[] createResponsePacket(byte[] deviceId, byte[] content) {
        // Protocol structure:
        // Header (3 bytes) + Type (1 byte) + Device ID (6 bytes) + Packet Length (2 bytes) + 
        // Content (N bytes) + Checksum (1 byte) + Trailer (3 bytes)
        
        final int HEADER_LENGTH = 3;
        final int TYPE_LENGTH = 1;
        final int DEVICE_ID_LENGTH = 6;
        final int PACKET_LENGTH_FIELD_LENGTH = 2;
        final int CONTENT_LENGTH = content.length;
        final int CHECKSUM_LENGTH = 1;
        final int TRAILER_LENGTH = 3;
        
        // Total packet length
        int totalLength = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + 
                         CONTENT_LENGTH + CHECKSUM_LENGTH + TRAILER_LENGTH;
        
        // Create packet buffer
        byte[] packet = new byte[totalLength];
        
        // Fill header
        System.arraycopy(HEADER, 0, packet, 0, HEADER_LENGTH);
        
        // Fill type
        packet[HEADER_LENGTH] = TYPE_CODE;
        
        // Fill device ID (pad with zeros if necessary)
        int deviceIdCopyLength = Math.min(deviceId.length, DEVICE_ID_LENGTH);
        System.arraycopy(deviceId, 0, packet, HEADER_LENGTH + TYPE_LENGTH, deviceIdCopyLength);
        // Pad with zeros if device ID is shorter than 6 characters
        for (int i = deviceIdCopyLength; i < DEVICE_ID_LENGTH; i++) {
            packet[HEADER_LENGTH + TYPE_LENGTH + i] = 0x30; // ASCII '0'
        }
        
        // Fill packet length (big endian)
        int packetLength = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + 
                          CONTENT_LENGTH + CHECKSUM_LENGTH;
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH] = (byte) ((packetLength >> 8) & 0xFF);
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + 1] = (byte) (packetLength & 0xFF);
        
        // Fill content
        System.arraycopy(content, 0, packet, HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + 
                        PACKET_LENGTH_FIELD_LENGTH, content.length);
        
        // Calculate and fill checksum
        byte checksum = calculateChecksumForResponsePacket(packet);
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + CONTENT_LENGTH] = checksum;
        
        // Fill trailer
        System.arraycopy(TRAILER, 0, packet, totalLength - TRAILER_LENGTH, TRAILER_LENGTH);
        
        return packet;
    }
    
    /**
     * Calculate checksum for a response packet (XOR of all bytes from header to content)
     * @param packet Packet bytes
     * @return Checksum byte
     */
    private byte calculateChecksumForResponsePacket(byte[] packet) {
        byte checksum = 0;
        
        // Calculate checksum: XOR of all bytes from header to content (excluding checksum, trailer)
        final int HEADER_LENGTH = 3;
        final int TYPE_LENGTH = 1;
        final int DEVICE_ID_LENGTH = 6;
        final int PACKET_LENGTH_FIELD_LENGTH = 2;
        final int CONTENT_LENGTH = packet.length - HEADER_LENGTH - TYPE_LENGTH - DEVICE_ID_LENGTH - 
                                  PACKET_LENGTH_FIELD_LENGTH - CHECKSUM_LENGTH - TRAILER.length;
        
        int checksumEndIndex = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + CONTENT_LENGTH;
        
        for (int i = 0; i < checksumEndIndex; i++) {
            checksum ^= packet[i];
        }
        
        return checksum;
    }
    
    /**
     * Save parsed data to database
     */
    private void saveDataToDatabase(byte[] packet) {
        try {
            // Parse the packet
            ProtocolDataParser.ParseResult parseResult = ProtocolDataParser.parsePacket(packet);
            
            // Save the main message log
            DtuFtpMessageLog messageLog = parseResult.getMessageLog();
            if (messageLog != null && dtuFtpMessageLogService != null) {
                messageLog = dtuFtpMessageLogService.saveMessageLog(messageLog);
                log.info("Saved message log to database with ID: {}", messageLog.getId());
                
                // Save detailed data based on command type
                if (parseResult.getSimDetail() != null && dtuFtpMessageDetailSimService != null) {
                    // Save SIM card data
                    DtuFtpMessageDetailSim simDetail = parseResult.getSimDetail();
                    simDetail.setFtpMessageLogId(messageLog.getId());
                    // Set the device ID (dtuNumber) in the SIM detail record
                    simDetail.setDtuNumber(messageLog.getDtuNumber());
                    dtuFtpMessageDetailSimService.saveMessageDetailSim(simDetail);
                    log.info("Saved SIM card detail to database");
                }
                
                if (parseResult.getGpsDetails() != null && !parseResult.getGpsDetails().isEmpty() && dtuFtpMessageDetailService != null) {
                    // Save GPS data
                    for (DtuFtpMessageDetail gpsDetail : parseResult.getGpsDetails()) {
                        gpsDetail.setFtpMessageLogId(messageLog.getId());
                        
                        // Extract device ID from message log
                        String deviceId = messageLog.getDtuNumber();
                        
                        // Save to device-specific table
                        DtuFtpMessageDetail savedDetail = dtuFtpMessageDetailService.saveMessageDetailToDeviceTable(gpsDetail, deviceId);
                        
                        // Send message to RabbitMQ after saving
                        sendToRabbitMQ(savedDetail);
                        
                        // Analyze transport start/end points
                        TransportAnalyzer.processMessageDetail(savedDetail);
                    }
                    log.info("Saved {} GPS details to device-specific tables", parseResult.getGpsDetails().size());
                } else if (parseResult.getGpsDetails().isEmpty() && dtuFtpMessageDetailService != null && 
                           parseResult.getMessageLog().getReceiveMessageType() == 0x02) {
                    // Even if no GPS details were parsed, create a minimal record with basic information for command 0x02
                    DtuFtpMessageDetail minimalDetail = new DtuFtpMessageDetail();
                    minimalDetail.setFtpMessageLogId(messageLog.getId());
                    
                    // Extract message count from the packet if possible
                    int messageNum = extractMessageCount(packet);
                    minimalDetail.setMessageNum(messageNum);
                    
                    // Try to extract other available information from the packet
                    extractAdditionalInfoFromPacket(packet, minimalDetail);
                    
                    // Extract device ID from message log
                    String deviceId = messageLog.getDtuNumber();
                    
                    // Save to device-specific table
                    DtuFtpMessageDetail savedDetail = dtuFtpMessageDetailService.saveMessageDetailToDeviceTable(minimalDetail, deviceId);
                    
                    // Send message to RabbitMQ after saving
                    sendToRabbitMQ(savedDetail);
                    
                    // Analyze transport start/end points
                    TransportAnalyzer.processMessageDetail(savedDetail);
                    
                    log.info("Saved minimal GPS detail to device-specific table with message count: {}", messageNum);
                }

            }
        } catch (Exception e) {
            log.error("Error saving data to database: {}", e.getMessage(), e);
        }
    }
    
    
    
    /**
     * Validate packet according to protocol
     */
    private boolean validatePacket(byte[] packet) {
        // Check header
        for (int i = 0; i < HEADER.length; i++) {
            if (packet[i] != HEADER[i]) {
                log.warn("Invalid header at position {}: expected 0x{} but got 0x{}", 
                         i, String.format("%02X", HEADER[i]), String.format("%02X", packet[i]));
                return false;
            }
        }
        
        // Check type code
        if (packet[HEADER.length] != TYPE_CODE) {
            log.warn("Invalid type code: expected 0x{} but got 0x{}", 
                     String.format("%02X", TYPE_CODE), String.format("%02X", packet[HEADER.length]));
            return false;
        }
        
        // Check minimum packet length (without end marker)
        if (packet.length < HEADER.length + 1 + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + 
            CHECKSUM_LENGTH + TRAILER.length) {
            log.warn("Packet too short to contain required fields. Length: {}", packet.length);
            return false;
        }
        
        // Detect end marker
        byte[] endMarker = detectEndMarker(packet);
        
        // Check if packet ends with 0x7B 0x7B, which should be ignored
        if (endMarker.length == 2 && endMarker[0] == 0x7B && endMarker[1] == 0x7B) {
            log.debug("Ignoring packet ending with 0x7B 0x7B");
            return false;
        }
        
        // Check trailer position based on whether end marker is present
        int trailerStartPos = packet.length - TRAILER.length;
        if (endMarker.length == 2) {
            trailerStartPos = packet.length - endMarker.length - TRAILER.length;
        }
        
        // Validate trailer
        for (int i = 0; i < TRAILER.length; i++) {
            int trailerIndex = trailerStartPos + i;
            if (trailerIndex >= packet.length || packet[trailerIndex] != TRAILER[i]) {
                log.warn("Invalid trailer at position {}: expected 0x{} but got 0x{}", 
                         trailerIndex, String.format("%02X", TRAILER[i]), 
                         trailerIndex < packet.length ? String.format("%02X", packet[trailerIndex]) : "N/A");
                return false;
            }
        }
        
        // Validate checksum
        byte calculatedChecksum = calculateChecksum(packet);
        int checksumPos = packet.length - TRAILER.length - CHECKSUM_LENGTH;
        
        // Adjust checksum position if end marker is present
        if (endMarker.length == 2) {
            checksumPos = packet.length - endMarker.length - TRAILER.length - CHECKSUM_LENGTH;
        }
        
        // Validate checksum if position is valid
        if (checksumPos >= 0 && checksumPos < packet.length && packet[checksumPos] != calculatedChecksum) {
            log.warn("Checksum mismatch: expected=0x{}, actual=0x{}", 
                     String.format("%02X", calculatedChecksum), 
                     String.format("%02X", packet[checksumPos]));
            return false;
        }
        
        return true;
    }
    
    /**
     * Extract content from packet
     */
    private byte[] extractContent(byte[] packet) {
        int contentStart = HEADER.length + 1 + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH;
        
        // Determine content end based on actual packet structure
        int contentEnd = packet.length;
        
        // Check if packet ends with standard end markers and adjust content end
        if (packet.length >= 2) {
            // Check for 0x0D, 0x0A end marker
            if (packet[packet.length - 2] == 0x0D && packet[packet.length - 1] == 0x0A) {
                contentEnd = packet.length - 2;
            }
            // Check for 0x7B, 0x7B end marker
            else if (packet[packet.length - 2] == 0x7B && packet[packet.length - 1] == 0x7B) {
                contentEnd = packet.length - 2;
            }
        }
        
        // Also exclude trailer and checksum bytes
        if (contentEnd >= TRAILER.length + CHECKSUM_LENGTH) {
            contentEnd = contentEnd - TRAILER.length - CHECKSUM_LENGTH;
        }
        
        // Ensure content start is not beyond content end
        if (contentStart >= contentEnd) {
            return new byte[0];
        }
        
        return Arrays.copyOfRange(packet, contentStart, contentEnd);
    }
    
    /**
     * Detect the end marker type in the packet
     */
    private byte[] detectEndMarker(byte[] packet) {
        // Check for standard end marker 0D 0A
        if (packet.length >= 2 && packet[packet.length - 2] == 0x0D && packet[packet.length - 1] == 0x0A) {
            return END_MARKER; // {0x0D, 0x0A}
        }
        
        // Check for alternative end marker 7B 7B (but we'll ignore these packets)
        if (packet.length >= 2 && packet[packet.length - 2] == 0x7B && packet[packet.length - 1] == 0x7B) {
            return new byte[]{0x7B, 0x7B};
        }
        
        // No end marker detected
        return new byte[0];
    }
    
    /**
     * Calculate checksum (XOR of all bytes from header to checksum position)
     */
    private byte calculateChecksum(byte[] packet) {
        byte checksum = 0;
        
        // Calculate checksum based on the actual packet structure
        int checksumIndex = packet.length - TRAILER.length - CHECKSUM_LENGTH;
        
        // Check if packet ends with end markers and adjust checksum index
        if (packet.length >= 2) {
            // Check for 0x0D, 0x0A end marker
            if (packet[packet.length - 2] == 0x0D && packet[packet.length - 1] == 0x0A) {
                checksumIndex = packet.length - 2 - TRAILER.length - CHECKSUM_LENGTH;
            }
            // We ignore packets ending with 0x7B, 0x7B, so no adjustment needed for them
        }
        
        // Calculate checksum
        for (int i = 0; i < checksumIndex; i++) {
            checksum ^= packet[i];
        }
        return checksum;
    }
    
    /**
     * Concatenate two byte arrays
     */
    private byte[] concatenate(byte[] a, byte[] b) {
        byte[] result = new byte[a.length + b.length];
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }
    
    /**
     * Convert byte array to hex string (static version)
     */
    private static String bytesToHexStatic(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }
    
    /**
     * Convert byte array to hex string
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }
    
    /**
     * Save data to file in logs/netty-log directory
     */
    private void saveDataToFile(String prefix, byte[] data, String description) {
        try {
            // Create the new log directory structure: netty-log/receive/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            Path logDir = Paths.get("netty-log", "receive", dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the new directory structure
            String filename = String.format("netty-log/receive/%s/%s_%s_%s.txt", dateStr, dateStr, prefix, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Description: %s%n", description));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHex(data)));
                writer.write(String.format("String Data: %s%n", new String(data).replaceAll("[^\\p{Print}]", ".")));
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved data to file: {}", filename);
        } catch (IOException e) {
            log.error("Failed to save data to file", e);
        }
    }
    
    /**
     * Save valid protocol data by device ID and date
     */
    private void saveValidDataByDevice(byte[] deviceId, byte[] data) {
        try {
            // Create directory structure: netty-log/device/{device_id}/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            String deviceIdStr = bytesToHex(deviceId).replace(" ", "");
            Path logDir = Paths.get("netty-log", "device", deviceIdStr, dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the new directory structure
            String filename = String.format("netty-log/device/%s/%s/valid_data_%s.txt", deviceIdStr, dateStr, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Device ID: %s%n", deviceIdStr));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHex(data)));
                writer.write(String.format("String Data: %s%n", new String(data).replaceAll("[^\\p{Print}]", ".")));
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved valid data by device to file: {}", filename);
        } catch (IOException e) {
            log.error("Failed to save valid data by device to file", e);
        }
    }
    
    /**
     * Save all received data to netty-log/all directory with timestamp labeling
     */
    private void saveAllDataToFile(byte[] data) {
        try {
            // Create the new log directory structure: netty-log/all/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            Path logDir = Paths.get("netty-log", "all", dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the new directory structure
            String filename = String.format("netty-log/all/%s/all_data_%s.txt", dateStr, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHex(data)));
                writer.write(String.format("String Data: %s%n", new String(data).replaceAll("[^\\p{Print}]", ".")));
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved all data to file: {}", filename);
            
            // Save data to database without strict protocol validation
            saveAllDataToDatabase(data);
            
        } catch (IOException e) {
            log.error("Failed to save all data to file", e);
        }
    }
    
    /**
     * Save all received data to database without strict protocol validation
     */
    private void saveAllDataToDatabase(byte[] packet) {
        try {
            // Parse the packet and save to database regardless of validation results
            ProtocolDataParser.ParseResult parseResult = ProtocolDataParser.parsePacket(packet);
            
            // Save the main message log
            DtuFtpMessageLog messageLog = parseResult.getMessageLog();
            if (messageLog != null && dtuFtpMessageLogService != null) {
                messageLog = dtuFtpMessageLogService.saveMessageLog(messageLog);
                log.info("Saved message log to database with ID: {}", messageLog.getId());
                
                // Save detailed data based on command type
                if (parseResult.getSimDetail() != null && dtuFtpMessageDetailSimService != null) {
                    // Save SIM card data
                    DtuFtpMessageDetailSim simDetail = parseResult.getSimDetail();
                    simDetail.setFtpMessageLogId(messageLog.getId());
                    simDetail.setDtuNumber(messageLog.getDtuNumber());
                    dtuFtpMessageDetailSimService.saveMessageDetailSim(simDetail);
                    log.info("Saved SIM card detail to database");
                }
                
                if (parseResult.getGpsDetails() != null && !parseResult.getGpsDetails().isEmpty() && dtuFtpMessageDetailService != null) {
                    // Save GPS data
                    for (DtuFtpMessageDetail gpsDetail : parseResult.getGpsDetails()) {
                        gpsDetail.setFtpMessageLogId(messageLog.getId());
                        // Extract device ID from message log
                        String deviceId = messageLog.getDtuNumber();
                        
                        // Save to device-specific table
                        DtuFtpMessageDetail savedDetail = dtuFtpMessageDetailService.saveMessageDetailToDeviceTable(gpsDetail, deviceId);
                        
                        // Send message to RabbitMQ after saving
                        sendToRabbitMQ(savedDetail);
                        
                        // Analyze transport start/end points
                        TransportAnalyzer.processMessageDetail(savedDetail);
                    }
                    log.info("Saved {} GPS details to database", parseResult.getGpsDetails().size());
                } else if (parseResult.getGpsDetails().isEmpty() && dtuFtpMessageDetailService != null && 
                           parseResult.getMessageLog().getReceiveMessageType() == 0x02) {
                    // Even if no GPS details were parsed, create a minimal record with basic information for command 0x02
                    DtuFtpMessageDetail minimalDetail = new DtuFtpMessageDetail();
                    minimalDetail.setFtpMessageLogId(messageLog.getId());
                    
                    // Extract message count from the packet if possible
                    int messageNum = extractMessageCount(packet);
                    minimalDetail.setMessageNum(messageNum);
                    
                    // Try to extract other available information from the packet
                    extractAdditionalInfoFromPacket(packet, minimalDetail);
                    
                    // Extract device ID from message log
                    String deviceId = messageLog.getDtuNumber();
                    
                    // Save to device-specific table
                    DtuFtpMessageDetail savedDetail = dtuFtpMessageDetailService.saveMessageDetailToDeviceTable(minimalDetail, deviceId);
                    
                    // Send message to RabbitMQ after saving
                    sendToRabbitMQ(savedDetail);
                    
                    // Analyze transport start/end points
                    TransportAnalyzer.processMessageDetail(savedDetail);
                    
                    log.info("Saved minimal GPS detail to device-specific table with message count: {}", messageNum);
                }
            }
        } catch (Exception e) {
            // Even if parsing fails, we still want to save the raw packet to the database
            try {
                // Create a minimal message log with just the raw data
                DtuFtpMessageLog messageLog = new DtuFtpMessageLog();
                messageLog.setReceiveMessage(packet);
                messageLog.setReceiveMessageType(0); // Unknown type
                messageLog.setDtuNumber("UNKNOWN"); // Unknown device
                
                if (dtuFtpMessageLogService != null) {
                    messageLog = dtuFtpMessageLogService.saveMessageLog(messageLog);
                    log.info("Saved raw packet to database with ID: {}", messageLog.getId());
                }
            } catch (Exception e2) {
                log.error("Failed to save raw packet to database", e2);
            }
            log.error("Failed to parse packet and save to database", e);
        }
    }
    
    /**
     * Save display command acknowledgment response to file in netty-log/response directory, 
     * organized by device and date
     * @param deviceId Device ID bytes
     * @param data Response data that was received
     */
    private static void saveDisplayAckResponseToFile(byte[] deviceId, byte[] data) {
        try {
            // Convert device ID bytes to string
            String deviceIdStr = new String(deviceId, java.nio.charset.StandardCharsets.US_ASCII).trim();
            
            // Create directory structure: netty-log/response/{device_id}/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            Path logDir = Paths.get("netty-log", "response", deviceIdStr, dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the directory structure
            String filename = String.format("netty-log/response/%s/%s/response_data_%s.txt", deviceIdStr, dateStr, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Device ID: %s%n", deviceIdStr));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHexStatic(data)));
                writer.write(String.format("String Data: %s%n", new String(data, java.nio.charset.StandardCharsets.US_ASCII).replaceAll("[^\\p{Print}]", ".")));
                
                // Add specific information about the acknowledgment
                if (data.length >= 2) {
                    byte result = data[1]; // Result byte: 0x01 - success, 0x00 - failure
                    writer.write(String.format("Acknowledgment Result: %s (0x%02X)%n", 
                            (result == 0x01) ? "SUCCESS" : "FAILURE", result));
                }
                
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved display acknowledgment response data to file: {}", filename);
        } catch (IOException e) {
            log.error("Failed to save display acknowledgment response data to file", e);
        }
    }
    
    /**
     * Extract message count from packet content
     * @param packet the raw packet data
     * @return message count or 0 if unable to extract
     */
    private int extractMessageCount(byte[] packet) {
        try {
            // Protocol structure:
            // Header (3 bytes) + Type (1 byte) + Device ID (6 bytes) + Packet Length (2 bytes) + Content + Checksum (1 byte) + Trailer (3 bytes) + End Marker (2 bytes)
            final int HEADER_LENGTH = 3;
            final int TYPE_LENGTH = 1;
            final int DEVICE_ID_LENGTH = 6;
            final int PACKET_LENGTH_FIELD_LENGTH = 2;
            final int CHECKSUM_LENGTH = 1;
            final int TRAILER_LENGTH = 3;
            final int END_MARKER_LENGTH = 2;
            
            // Calculate content start position
            int contentStart = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH;
            
            // Calculate checksum position (end of content)
            int checksumIndex = packet.length - END_MARKER_LENGTH - TRAILER_LENGTH - CHECKSUM_LENGTH;
            
            // Make sure we have enough data
            if (contentStart + 3 <= checksumIndex) {
                // Message count is at the 3rd byte of content (index 2)
                return packet[contentStart + 2] & 0xFF;
            }
        } catch (Exception e) {
            log.warn("Failed to extract message count from packet: {}", e.getMessage());
        }
        return 0;
    }
    
    /**
     * Extract additional information from packet when GPS parsing fails
     * @param packet the raw packet data
     * @param detail the detail entity to populate
     */
    private void extractAdditionalInfoFromPacket(byte[] packet, DtuFtpMessageDetail detail) {
        try {
            // Protocol structure:
            // Header (3 bytes) + Type (1 byte) + Device ID (6 bytes) + Packet Length (2 bytes) + Content + Checksum (1 byte) + Trailer (3 bytes) + End Marker (2 bytes)
            final int HEADER_LENGTH = 3;
            final int TYPE_LENGTH = 1;
            final int DEVICE_ID_LENGTH = 6;
            final int PACKET_LENGTH_FIELD_LENGTH = 2;
            final int CHECKSUM_LENGTH = 1;
            final int TRAILER_LENGTH = 3;
            final int END_MARKER_LENGTH = 2;
            
            // Calculate content start position
            int contentStart = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH;
            
            // Calculate checksum position (end of content)
            int checksumIndex = packet.length - END_MARKER_LENGTH - TRAILER_LENGTH - CHECKSUM_LENGTH;
            
            // Extract content
            if (contentStart + 3 <= checksumIndex) {
                byte[] content = Arrays.copyOfRange(packet, contentStart, checksumIndex);
                
                // Set current time as default
                detail.setTime(new Date());
                
                // If we have at least one record, try to extract information from the first record
                if (content.length >= 3 + 40) { // Command byte + message count + at least one record
                    // Try to extract timestamp from the first record (6 bytes at offset 3)
                    try {
                        byte[] timeBytes = Arrays.copyOfRange(content, 3, 9); // 6 bytes for timestamp
                        Date parsedTime = parseHexTimestamp(timeBytes);
                        if (parsedTime != null) {
                            detail.setTime(parsedTime);
                        }
                    } catch (Exception e) {
                        log.debug("Failed to extract timestamp from packet: {}", e.getMessage());
                    }
                    
                    // Try to extract status information (1 byte at offset 10)
                    try {
                        if (content.length > 10) {
                            int statusBit = content[10] & 0xFF;
                            detail.setStatusBit(String.format("%02X", statusBit));
                            
                            // Parse individual status bits
                            detail.setPositioningStatus((statusBit & 0x01) != 0);
                            detail.setAlarmPowerStatus((statusBit & 0x10) != 0); // Bit4
                            detail.setVibrationStatus((statusBit & 0x08) != 0); // Bit3
                        }
                    } catch (Exception e) {
                        log.debug("Failed to extract status information from packet: {}", e.getMessage());
                    }
                    
                    // Try to extract other available information
                    try {
                        // Random time sequence (1 byte at offset 9)
                        if (content.length > 9) {
                            byte randTimeByte = content[9];
                            detail.setRandTimeSort(String.format("%02X", randTimeByte));
                        }
                        
                        // Radar distance (2 bytes at offset 11-12)
                        if (content.length > 12) {
                            int radarDistance = ((content[11] & 0xFF) << 8) | (content[12] & 0xFF);
                            detail.setRadarDistance((double) radarDistance);
                        }
                        
                        // Speed (2 bytes BCD at offset 17-18)
                        if (content.length > 18) {
                            int speedHigh = content[17] & 0xFF;
                            int speedLow = content[18] & 0xFF;
                            int speed = (parseBcd((byte) speedHigh) * 100) + parseBcd((byte) speedLow);
                            detail.setSpeed(java.math.BigDecimal.valueOf((double) speed / 10.0)); // Convert to km/h (0.1km/h unit)
                        }
                        
                        // Direction (2 bytes BCD at offset 19-20)
                        if (content.length > 20) {
                            int directionHigh = content[19] & 0xFF;
                            int directionLow = content[20] & 0xFF;
                            int direction = (parseBcd((byte) directionHigh) * 100) + parseBcd((byte) directionLow);
                            detail.setDirection(direction);
                        }
                        
                        // Satellite number (1 byte BCD at offset 21)
                        if (content.length > 21) {
                            int satelliteNum = parseBcd(content[21]);
                            detail.setSatelliteNum(satelliteNum);
                        }
                    } catch (Exception e) {
                        log.debug("Failed to extract additional information from packet: {}", e.getMessage());
                    }
                }
            }
        } catch (Exception e) {
            log.warn("Failed to extract additional information from packet: {}", e.getMessage());
        }
    }
    
    /**
     * Parse hexadecimal timestamp
     * Format: YY MM DD HH MM SS (6 bytes)
     * @param timeBytes 6 byte timestamp data
     * @return parsed Date or null if failed
     */
    private Date parseHexTimestamp(byte[] timeBytes) {
        try {
            if (timeBytes.length < 6) {
                return null;
            }
            
            // Parse hexadecimal
            int year = timeBytes[0] & 0xFF; // Year last two digits
            int month = timeBytes[1] & 0xFF; // Month
            int day = timeBytes[2] & 0xFF;   // Day
            int hour = timeBytes[3] & 0xFF;  // Hour
            int minute = timeBytes[4] & 0xFF; // Minute
            int second = timeBytes[5] & 0xFF; // Second
            
            // Convert to full year (assuming 20xx)
            year += 2000;
            
            // Construct datetime - device sends timestamps in local time (Asia/Shanghai)
            LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hour, minute, second);
            
            // Convert to Date using Asia/Shanghai timezone
            java.time.ZoneId shanghaiZone = java.time.ZoneId.of("Asia/Shanghai");
            return Date.from(localDateTime.atZone(shanghaiZone).toInstant());
        } catch (Exception e) {
            log.debug("Failed to parse hex timestamp: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * Parse BCD code
     * @param b BCD byte
     * @return parsed decimal value
     */
    private int parseBcd(byte b) {
        int high = (b & 0xF0) >> 4;
        int low = b & 0x0F;
        return high * 10 + low;
    }
    
    /**
     * Update device ID in connection info
     * @param ctx Channel handler context
     * @param deviceId Device ID bytes
     */
    private void updateDeviceIdInConnectionInfo(ChannelHandlerContext ctx, byte[] deviceId) {
        try {
            // Get channel ID
            String channelId = ctx.channel().id().asShortText();
            
            // Convert device ID to string
            String deviceIdStr = new String(deviceId, java.nio.charset.StandardCharsets.US_ASCII).trim();
            
            // Update device to channel mapping
            deviceToChannelMap.put(deviceIdStr, channelId);
            
            // Update device ID in connection info
            if (dtuFtpInfoService != null) {
                // Check if there's an existing record for this device
                DtuFtpInfo existingInfo = dtuFtpInfoService.getOne(new QueryWrapper().where("device_id = ?", deviceIdStr));
                
                // Extract remote IP and port
                String remoteAddress = ctx.channel().remoteAddress().toString();
                String[] addressParts = remoteAddress.replace("/", "").split(":");
                String remoteIp = addressParts[0];
                int remotePort = Integer.parseInt(addressParts[1]);
                
                if (existingInfo != null) {
                    // Device already exists in the table, update the existing record with new connection info
                    existingInfo.setRemoteIp(remoteIp);
                    existingInfo.setRemotePort(remotePort);
                    existingInfo.setChannelId(channelId);
                    existingInfo.setServerChannelId(ctx.channel().parent().id().asShortText());
                    existingInfo.setConnectTime(new Date());
                    existingInfo.setStatus(1); // Online
                    existingInfo.setDisconnectTime(null); // Clear disconnect time
                    existingInfo.setUpdateTime(new Date());
                    
                    dtuFtpInfoService.updateById(existingInfo);
                    log.info("Updated existing connection info for device {}: channel ID = {}", deviceIdStr, channelId);
                } else {
                    // Device does not exist, create a new record
                    DtuFtpInfo dtuFtpInfo = new DtuFtpInfo();
                    dtuFtpInfo.setDeviceId(deviceIdStr);
                    dtuFtpInfo.setRemoteIp(remoteIp);
                    dtuFtpInfo.setRemotePort(remotePort);
                    dtuFtpInfo.setChannelId(channelId);
                    dtuFtpInfo.setServerChannelId(ctx.channel().parent().id().asShortText());
                    dtuFtpInfo.setConnectTime(new Date());
                    dtuFtpInfo.setStatus(1); // Online
                    dtuFtpInfo.setCreateTime(new Date());
                    dtuFtpInfo.setUpdateTime(new Date());
                    
                    dtuFtpInfoService.saveDtuFtpInfo(dtuFtpInfo);
                    log.info("Created new connection info for device {}: channel ID = {}", deviceIdStr, channelId);
                }
            } else {
                log.warn("DtuFtpInfoService is null, unable to update device ID");
            }
        } catch (Exception e) {
            log.error("Error updating device ID in connection info: {}", e.getMessage(), e);
        }
    }
    
    /**
     * Send remote display content command to a device
     * @param deviceId Device ID
     * @param displayContent 3-byte display content
     * @return true if command was sent successfully, false otherwise
     */
    public static boolean sendRemoteDisplayContent(String deviceId, byte[] displayContent) {
        try {
            // Validate display content length
            if (displayContent.length != 3) {
                log.warn("Invalid display content length: expected 3 bytes, got {}", displayContent.length);
                return false;
            }
            
            // Validate that display content consists of valid ASCII characters
            for (int i = 0; i < displayContent.length; i++) {
                if (displayContent[i] < 32 || displayContent[i] > 126) {
                    log.warn("Invalid display content character at position {}: byte value {}", i, displayContent[i]);
                    // Replace invalid characters with space
                    displayContent[i] = 0x20; // Space character
                }
            }
            
            // Create complete packet according to protocol
            byte[] packet = createDisplayContentPacket(deviceId, displayContent);
            
            log.info("Sending remote display content command to device {}: {}", deviceId, bytesToHexStatic(packet));
            
            // Add a more substantial delay to ensure device is ready to receive the command
            // This addresses the issue where commands are sent too quickly after connection
            try {
                Thread.sleep(300); // Increased from 100ms to 300ms delay
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            
            // Try to send data with retry logic
            boolean result = false;
            int retryCount = 0;
            final int maxRetries = 3;
            
            while (!result && retryCount < maxRetries) {
                if (retryCount > 0) {
                    // Add a delay between retries
                    try {
                        Thread.sleep(500); // Increased delay between retries
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                    log.debug("Retrying to send remote display content to device {} (attempt {})", 
                            deviceId, retryCount + 1);
                }
                
                result = sendDataToDevice(deviceId, packet);
                retryCount++;
            }
            
            if (result) {
                log.info("Successfully sent remote display content command '{}' to device {}", 
                        new String(displayContent, java.nio.charset.StandardCharsets.US_ASCII), deviceId);
            } else {
                log.warn("Failed to send remote display content command '{}' to device {} after {} attempts", 
                        new String(displayContent, java.nio.charset.StandardCharsets.US_ASCII), deviceId, maxRetries);
            }
            
            return result;
        } catch (Exception e) {
            log.error("Error sending remote display content to device {}: {}", deviceId, e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * Enhanced version of sendRemoteDisplayContent with acknowledgment tracking
     * @param deviceId Device ID
     * @param displayContent 3-byte display content
     * @param waitForAck Whether to wait for acknowledgment from device
     * @return true if command was sent successfully (and acknowledged if requested), false otherwise
     */
    public static boolean sendRemoteDisplayContent(String deviceId, byte[] displayContent, boolean waitForAck) {
        try {
            // Validate display content length
            if (displayContent.length != 3) {
                log.warn("Invalid display content length: expected 3 bytes, got {}", displayContent.length);
                return false;
            }
            
            // Create complete packet according to protocol
            byte[] packet = createDisplayContentPacket(deviceId, displayContent);
            
            log.info("Sending remote display content command to device {}: {}", deviceId, bytesToHexStatic(packet));
            
            // If waiting for acknowledgment, register the command
            String commandKey = null;
            if (waitForAck) {
                commandKey = deviceId + "_" + bytesToHexStatic(displayContent).replace(" ", "");
                pendingCommands.put(commandKey, System.currentTimeMillis());
                log.debug("Registered pending command: {}", commandKey);
            }
            
            // Add a more substantial delay to ensure device is ready to receive the command
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            
            // Try to send data with retry logic
            boolean result = false;
            int retryCount = 0;
            final int maxRetries = 3;
            
            while (!result && retryCount < maxRetries) {
                if (retryCount > 0) {
                    // Add a delay between retries
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                    log.debug("Retrying to send remote display content to device {} (attempt {})", 
                            deviceId, retryCount + 1);
                }
                
                result = sendDataToDevice(deviceId, packet);
                retryCount++;
            }
            
            if (result) {
                log.info("Successfully sent remote display content command '{}' to device {}", 
                        new String(displayContent, java.nio.charset.StandardCharsets.US_ASCII), deviceId);
                
                // If waiting for acknowledgment, wait for it
                if (waitForAck && commandKey != null) {
                    result = waitForCommandAcknowledgment(commandKey);
                }
            } else {
                log.warn("Failed to send remote display content command '{}' to device {} after {} attempts", 
                        new String(displayContent, java.nio.charset.StandardCharsets.US_ASCII), deviceId, maxRetries);
                // Remove pending command if it was registered
                if (commandKey != null) {
                    pendingCommands.remove(commandKey);
                }
            }
            
            return result;
        } catch (Exception e) {
            log.error("Error sending remote display content to device {}: {}", deviceId, e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * Wait for command acknowledgment from device
     * @param commandKey The command key to wait for
     * @return true if acknowledgment received, false otherwise
     */
    private static boolean waitForCommandAcknowledgment(String commandKey) {
        log.debug("Waiting for acknowledgment of command: {}", commandKey);
        long startTime = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - startTime < COMMAND_ACK_TIMEOUT) {
            if (!pendingCommands.containsKey(commandKey)) {
                // Command was acknowledged
                log.debug("Received acknowledgment for command: {}", commandKey);
                return true;
            }
            
            try {
                Thread.sleep(100); // Check every 100ms
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        
        // Timeout reached
        pendingCommands.remove(commandKey);
        log.warn("Timeout waiting for acknowledgment of command: {}", commandKey);
        return false;
    }
    
    /**
     * Process acknowledgment from device
     * @param deviceId Device ID
     * @param ackContent Acknowledgment content
     */
    public static void processCommandAcknowledgment(String deviceId, byte[] ackContent) {
        if (ackContent.length >= 3) {
            // Extract the display content that was acknowledged
            byte[] displayContent = new byte[3];
            System.arraycopy(ackContent, 0, displayContent, 0, 3);
            String commandKey = deviceId + "_" + bytesToHexStatic(displayContent).replace(" ", "");
            
            // Remove from pending commands if it exists
            if (pendingCommands.remove(commandKey) != null) {
                log.debug("Processed acknowledgment for command: {}", commandKey);
            } else {
                log.debug("Received acknowledgment for unknown command: {}", commandKey);
            }
        }
    }
    
    /**
     * Create a complete packet for display content command according to protocol
     * @param deviceId Device ID
     * @param displayContent 3-byte display content
     * @return Complete packet bytes
     */
    private static byte[] createDisplayContentPacket(String deviceId, byte[] displayContent) {
        // Protocol structure:
        // Header (3 bytes) + Type (1 byte) + Device ID (6 bytes) + Packet Length (2 bytes) + 
        // Content (4 bytes: 1 command byte + 3 display content bytes) + Checksum (1 byte) + Trailer (3 bytes)
        
        final int HEADER_LENGTH = 3;
        final int TYPE_LENGTH = 1;
        final int DEVICE_ID_LENGTH = 6;
        final int PACKET_LENGTH_FIELD_LENGTH = 2;
        final int CONTENT_LENGTH = 4; // 1 command byte + 3 display content bytes
        final int CHECKSUM_LENGTH = 1;
        final int TRAILER_LENGTH = 3;
        
        // Total packet length
        int totalLength = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + 
                         CONTENT_LENGTH + CHECKSUM_LENGTH + TRAILER_LENGTH;
        
        // Create packet buffer
        byte[] packet = new byte[totalLength];
        
        // Fill header
        System.arraycopy(HEADER, 0, packet, 0, HEADER_LENGTH);
        
        // Fill type
        packet[HEADER_LENGTH] = TYPE_CODE;
        
        // Fill device ID (pad with zeros if necessary)
        byte[] deviceIdBytes = deviceId.getBytes(java.nio.charset.StandardCharsets.US_ASCII);
        int deviceIdCopyLength = Math.min(deviceIdBytes.length, DEVICE_ID_LENGTH);
        System.arraycopy(deviceIdBytes, 0, packet, HEADER_LENGTH + TYPE_LENGTH, deviceIdCopyLength);
        // Pad with zeros if device ID is shorter than 6 characters
        for (int i = deviceIdCopyLength; i < DEVICE_ID_LENGTH; i++) {
            packet[HEADER_LENGTH + TYPE_LENGTH + i] = 0x30; // ASCII '0'
        }
        
        // Fill packet length (big endian)
        int packetLength = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + 
                          CONTENT_LENGTH + CHECKSUM_LENGTH;
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH] = (byte) ((packetLength >> 8) & 0xFF);
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + 1] = (byte) (packetLength & 0xFF);
        
        // Fill content (command byte + display content)
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH] = 0x04; // Command byte
        System.arraycopy(displayContent, 0, packet, HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + 
                        PACKET_LENGTH_FIELD_LENGTH + 1, displayContent.length);
        
        // Calculate and fill checksum
        byte checksum = calculateChecksumForPacket(packet);
        packet[HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + CONTENT_LENGTH] = checksum;
        
        // Fill trailer
        System.arraycopy(TRAILER, 0, packet, totalLength - TRAILER_LENGTH, TRAILER_LENGTH);
        
        return packet;
    }
    
    /**
     * Calculate checksum for a packet (XOR of all bytes from header to content)
     * @param packet Packet bytes
     * @return Checksum byte
     */
    private static byte calculateChecksumForPacket(byte[] packet) {
        byte checksum = 0;
        
        // Calculate checksum: XOR of all bytes from header to content (excluding checksum, trailer)
        final int HEADER_LENGTH = 3;
        final int TYPE_LENGTH = 1;
        final int DEVICE_ID_LENGTH = 6;
        final int PACKET_LENGTH_FIELD_LENGTH = 2;
        final int CONTENT_LENGTH = 4; // 1 command byte + 3 display content bytes
        final int CHECKSUM_LENGTH = 1;
        
        int checksumEndIndex = HEADER_LENGTH + TYPE_LENGTH + DEVICE_ID_LENGTH + PACKET_LENGTH_FIELD_LENGTH + CONTENT_LENGTH;
        
        for (int i = 0; i < checksumEndIndex; i++) {
            checksum ^= packet[i];
        }
        
        return checksum;
    }

    /**
     * Check if a device is currently connected
     * @param deviceId Device ID
     * @return true if device is connected, false otherwise
     */
    public static boolean isDeviceConnected(String deviceId) {
        String channelId = deviceToChannelMap.get(deviceId);
        if (channelId == null) {
            return false;
        }
        
        ChannelHandlerContext ctx = channelContextMap.get(channelId);
        if (ctx == null) {
            return false;
        }
        
        // Check if channel is active
        return ctx.channel().isActive();
    }
    
    /**
     * Get the list of connected devices
     * @return Array of connected device IDs
     */
    public static String[] getConnectedDevices() {
        return deviceToChannelMap.keySet().toArray(new String[0]);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("Exception in binary data handler", cause);
        ctx.close();
    }
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("Client connected: {}", ctx.channel().remoteAddress());
        
        try {
            // Store channel context for sending messages back to the device
            String channelId = ctx.channel().id().asShortText();
            channelContextMap.put(channelId, ctx);
            
            // Extract remote IP and port
            String remoteAddress = ctx.channel().remoteAddress().toString();
            String[] addressParts = remoteAddress.replace("/", "").split(":");
            String remoteIp = addressParts[0];
            int remotePort = Integer.parseInt(addressParts[1]);
            
            // Get channel IDs
            String serverChannelId = ctx.channel().parent().id().asShortText();
            
            if (dtuFtpInfoService != null) {
                // Don't create a record here - wait until we get the device ID from the first data packet
                // The device ID will be used to either create a new record or update an existing one
                log.info("Client connected, will create/update record when device ID is received: remote IP={}, port={}", remoteIp, remotePort);
            } else {
                log.warn("DtuFtpInfoService is null, unable to manage connection info");
            }
        } catch (Exception e) {
            log.error("Error handling client connection: {}", e.getMessage(), e);
        }
        
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("Client disconnected: {}", ctx.channel().remoteAddress());
        
        try {
            // Remove channel context
            String channelId = ctx.channel().id().asShortText();
            channelContextMap.remove(channelId);
            
            // Remove device to channel mapping if exists
            deviceToChannelMap.values().removeIf(id -> id.equals(channelId));
            
            // Update disconnect info
            if (dtuFtpInfoService != null) {
                int updatedCount = dtuFtpInfoService.updateDisconnectInfoByChannelId(channelId, new Date());
                if (updatedCount > 0) {
                    log.info("Updated disconnect info for channel {}: {} records updated", channelId, updatedCount);
                } else {
                    log.warn("No active connection found for channel {} to update disconnect info", channelId);
                }
            } else {
                log.warn("DtuFtpInfoService is null, unable to update disconnect info");
            }
        } catch (Exception e) {
            log.error("Error updating disconnect info: {}", e.getMessage(), e);
        }
        
        super.channelInactive(ctx);
    }
    
    /**
     * Send data to a specific device by device ID
     * @param deviceId Device ID
     * @param data Data to send
     * @return true if data was sent successfully, false otherwise
     */
    public static boolean sendDataToDevice(String deviceId, byte[] data) {
        try {
            // Get channel ID for the device
            String channelId = deviceToChannelMap.get(deviceId);
            if (channelId == null) {
                log.warn("No channel found for device ID: {}", deviceId);
                return false;
            }
            
            // Get channel context
            ChannelHandlerContext ctx = channelContextMap.get(channelId);
            if (ctx == null) {
                log.warn("No channel context found for channel ID: {}", channelId);
                return false;
            }
            
            // Check if channel is active before sending
            if (!ctx.channel().isActive()) {
                log.warn("Channel is not active for device ID: {}", deviceId);
                return false;
            }
            
            // Additional check: ensure channel is writable
            if (!ctx.channel().isWritable()) {
                log.warn("Channel is not writable for device ID: {}", deviceId);
                return false;
            }
            
            // Log the channel state before sending
            log.debug("Channel state for device {}: active={}, writable={}", 
                    deviceId, ctx.channel().isActive(), ctx.channel().isWritable());
            
            // Send data
            ctx.writeAndFlush(data);
            log.info("Sent {} bytes to device {}: {}", data.length, deviceId, bytesToHexStatic(data));
            
            // Save sent data to log file
            saveSentDataToFile(deviceId, data, "Data sent to device");
            
            // Add a small delay after sending to ensure device has time to process
            try {
                Thread.sleep(150); // Increased from 100ms to 150ms
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            
            return true;
        } catch (Exception e) {
            log.error("Error sending data to device {}: {}", deviceId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * Save sent data to file in netty-log/send directory, organized by device and date
     * @param deviceId Device ID
     * @param data Data that was sent
     * @param description Description of the data
     */
    private static void saveSentDataToFile(String deviceId, byte[] data, String description) {
        try {
            // Create directory structure: netty-log/send/{device_id}/{date}
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String dateStr = dateFormat.format(new Date());
            Path logDir = Paths.get("netty-log", "send", deviceId, dateStr);
            
            // Ensure log directory exists
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            
            // Generate filename with timestamp
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS");
            String timeStr = timeFormat.format(new Date());
            
            // Create filename in the directory structure
            String filename = String.format("netty-log/send/%s/%s/sent_data_%s.txt", deviceId, dateStr, timeStr);
            
            // Write data to file
            try (FileWriter writer = new FileWriter(filename, true)) {
                writer.write(String.format("Timestamp: %s%n", new Date()));
                writer.write(String.format("Description: %s%n", description));
                writer.write(String.format("Data Length: %d bytes%n", data.length));
                writer.write(String.format("Hex Data: %s%n", bytesToHexStatic(data)));
                writer.write(String.format("String Data: %s%n", new String(data, java.nio.charset.StandardCharsets.US_ASCII).replaceAll("[^\\p{Print}]", ".")));
                writer.write("----------------------------------------\n");
            }
            
            log.debug("Saved sent data to file: {}", filename);
        } catch (IOException e) {
            log.error("Failed to save sent data to file", e);
        }
    }

    /**
     * Send message to RabbitMQ after saving to database
     * @param messageDetail the saved message detail
     */
    private static void sendToRabbitMQ(DtuFtpMessageDetail messageDetail) {
        try {
            // Add null check to prevent NullPointerException
            if (messageDetail == null) {
                log.error("Cannot send null message detail to RabbitMQ");
                return;
            }
            
            // Add null check for ID to prevent NullPointerException
            if (messageDetail.getId() == null) {
                log.error("Cannot send message detail with null ID to RabbitMQ");
                return;
            }
            
            if (rabbitTemplate != null) {
                // Send message without expiration (TTL has been removed)
                rabbitTemplate.convertAndSend("dtu.record.exchange", "dtu.record.queue", 
                        messageDetail.getId());
                log.info("Sent message detail ID {} to RabbitMQ without TTL", 
                        messageDetail.getId());
            } else {
                log.warn("RabbitTemplate is null, unable to send message to RabbitMQ");
            }
        } catch (Exception e) {
            log.error("Error sending message detail ID {} to RabbitMQ: {}", 
                     messageDetail.getId() != null ? messageDetail.getId() : "NULL", 
                     e.getMessage(), e);
        }
    }
}